Grazing effects are multi-trophic through mediating plant composition: a meta-analysis



Alessandro Filazzola, Batbaatar Amgaa, Charlotte Brown, Issac Heida, Jessica Grenke, Margarete Dettlaff, Tan Bao, & JC Cahill

library(tidyverse)
library(PRISMAstatement)

Purpose

Conduct a meta-analysis of the literature testing the indirect effects of grazing on animal taxa’s through the direct effects on the plant community.

Objectives

Timeline

date task
Nov 9 Establish search terms to be used in the meta-analysis
Nov 12 Compile list of journal artcles and sub-divide for each researcher
Nov 14 Begin reviewing papers and extracting data
Jan 28 Complete data extraction from papers
Feb 11 Complete preliminary analysis and set structure for MS
Feb 25 Settle on analyses to be used and begin writing manuscript
March 11 Complete first draft of MS and pass to co-authors
March 25 Comments passed back on draft
April 2 Complete revisions and submit to journal

Literature Review - 2. Sort

This steps includes a. checking for duplicating, b. reviewing each instance for relevancy, c. consistently identifying and documenting exclusion criteria. Outcomes include a list of publications to be used for synthesis, a library of pdfs, and a PRISMA report to ensure the worflow is transparent and reproducible. Papers were excluded with the following characteristics:

  • Not emperical study (e.g. review, book chapter)
  • Irrelevant categories (e.g. political science, law, sports tourism, art)

Reasons for exclusion

evidence <- read.csv("data//synthesisdata//evidence.csv")
### Identify studies that were excluded
excludes <- evidence %>% group_by(reason.simplified) %>% count(exclude) %>% filter(reason.simplified!="")
ggplot(excludes, aes(x=reason.simplified, y=n)) + geom_bar(stat="identity") + coord_flip()

## frequency of study
year.rate <- evidence %>% group_by(Publication.Year) %>% summarize(n=length(Publication.Year))

ggplot(tail(year.rate,30)) + geom_bar(aes(x=Publication.Year, y=n), stat="identity") + ylab("number of published studies") +xlab("year published") +theme(text = element_text(size=16))

Prisma report

## total number of papers found
nrow(evidence)
## [1] 2989
## number of papers found outside of WoS
other <- read.csv("data/synthesisdata//other.sources.csv")
nrow(other)
## [1] 0
## number of articles excluded
excludes <- evidence %>% filter(exclude=="yes")
nrow(excludes)
## [1] 2679
## relevant papers
review <- evidence %>% filter(exclude!="yes")
nrow(review)
## [1] 310
## papers for meta
datasets <- read.csv("data//binary.simplified.csv")
meta <- length(unique(datasets$uniqueID))
meta
## [1] 216
prisma(found = 2989,
       found_other = 1,
       no_dupes = 2989,
       screened = 2989,
       screen_exclusions = 2675,
       full_text = 315,
       full_text_exclusions = 0,
       qualitative = 315, 
       quantitative = 216,
       width = 800, height = 800)
## Warning in prisma(found = 2989, found_other = 1, no_dupes = 2989, screened
## = 2989, : After screening exclusions, a different number of remaining full-
## text articles is stated.

Patterns of GI Studies Globally

require(ggmap)
###  Start with base map of world
mp <- NULL
mapWorld <- borders("world", colour="gray50", fill="gray50") # create a layer of borders
mp <- ggplot() +   mapWorld

## colorblind-friendly palette
cbPalette <- c("#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7","#000000")

meta <- read.csv("data//synthesisdata//evidence.csv")
meta <- meta %>% filter(lat != "") ## remove blanks for lat and lon
meta <- meta[!grepl(";", meta$lat),] ## remove multiple entries
meta$lat <- as.numeric(levels(meta$lat))[meta$lat] ## convert from factor to number
meta$lon <- as.numeric(levels(meta$lon))[meta$lon] ## convert from factor to number


## plot points on top
mp <- mp+ geom_point(data=meta , aes(x=lon, y=lat), size=2) 
mp

3. Synthesis

meta <- read.csv("data//binary.simplified.csv")


## Load packages and functions
library(reshape2)
## Warning: package 'reshape2' was built under R version 3.5.2
## 
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
## 
##     smiths
library(metafor)
## Warning: package 'metafor' was built under R version 3.5.2
## Loading required package: Matrix
## 
## Attaching package: 'Matrix'
## The following object is masked from 'package:tidyr':
## 
##     expand
## Loading 'metafor' package (version 2.0-0). For an overview 
## and introduction to the package please type: help(metafor).
source("meta.eval.r") ## Multiple aggregate


## Simplify the estimates column
meta2 <- meta
est <- read.csv("data//Unique.Estimates.Column.csv")
meta2 <- merge(meta2, est, by="Estimate")

## Create Unique identifier column
meta2[,"UniqueSite"] <- paste(meta2$uniqueID, meta2$Taxa.1, meta2$Functional.Group, meta2$estimate.simplified, sep="-")

## convert se to sd
meta2[meta2$Stat=="se","Value"] <- meta2[meta2$Stat=="se","Value"] * 1.96
meta2[meta2$Stat=="se","Stat"] <- "sd"

## drop comparisons that are not pairwise
meta2 <-  meta2 %>% filter(grazing.compare.simple == "ungrazed" | grazing.compare.simple == "grazed")
meta2 <- meta2 %>% filter(uniqueID != "30") ## drop study 30 because anomalous 

## Drop plants
meta2 <- meta2 %>% filter(Taxa.1 != "Plants") ## examine animals only


## Use function to extract summary statistics for comparisons
## meta.eval  arguments are (meta.data, compare, ids , stats)
grazed.compare <- meta.eval(meta2, grazing.compare.simple, UniqueSite, Stat)

## Combine the lists into same dataframe
## Rename Columns in second dataframe
grazed.stat <- grazed.compare [[2]] ## extracted statistics 
names(grazed.stat) <- c("UniqueSite","grazed_mean","grazed_sd","ungrazed_mean","ungrazed_sd","grazed_n","ungrazed_n") ## rename columns to match
grazed.raw <- grazed.compare[[1]] ## calculated statistics from raw values

## Join two dataframes
meta.stat <- rbind(grazed.raw, grazed.stat[, names(grazed.raw)])


meta.ready <- escalc(n1i = ungrazed_n, n2i = grazed_n, m1i = ungrazed_mean, m2i = grazed_mean, sd1i = ungrazed_sd, sd2i = grazed_sd, data = meta.stat, measure = "SMD", append = TRUE)
## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'
## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'

## Warning in ifelse(mi <= 1, NA, exp(lgamma(mi/2) - log(sqrt(mi/2)) -
## lgamma((mi - : value out of range in 'lgamma'
## Warning in escalc.default(n1i = ungrazed_n, n2i = grazed_n, m1i =
## ungrazed_mean, : Some 'yi' and/or 'vi' values equal to +-Inf. Recoded to
## NAs.
## clean up meta.ready
meta.ready <- na.omit(meta.ready) ## drop NAs
meta.ready <- meta.ready[meta.ready$grazed_mean<80,] ## Drop extreme high variance


## separate out the identifiers
siteID <- matrix(unlist(strsplit(meta.ready$UniqueSite,"-")),ncol=4, byrow=TRUE)
siteID <- data.frame(siteID) ## recreate as dataframe
colnames(siteID) <- c("Study","taxa","FG","measure") ## add column names
meta.ready <- cbind(data.frame(meta.ready), siteID)

#random-effects meta-analysis for grazed vs ungrazed plots
m1 <- rma(yi=yi, vi=vi,  data = meta.ready)
summary(m1) 
## 
## Random-Effects Model (k = 247; tau^2 estimator: REML)
## 
##    logLik   deviance        AIC        BIC       AICc  
## -381.6350   763.2700   767.2700   774.2806   767.3194  
## 
## tau^2 (estimated amount of total heterogeneity): 0.9099 (SE = 0.1079)
## tau (square root of estimated tau^2 value):      0.9539
## I^2 (total heterogeneity / total variability):   87.14%
## H^2 (total variability / sampling variability):  7.78
## 
## Test for Heterogeneity: 
## Q(df = 246) = 1563.6783, p-val < .0001
## 
## Model Results:
## 
## estimate      se    zval    pval   ci.lb   ci.ub     
##   0.2958  0.0702  4.2115  <.0001  0.1581  0.4334  ***
## 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#mixed-effects meta-analysis for grazed vs ungrazed plots
m2 <- rma(yi=yi, vi=vi, mods=~FG-1,  data = meta.ready)
summary(m2) 
## 
## Mixed-Effects Model (k = 247; tau^2 estimator: REML)
## 
##    logLik   deviance        AIC        BIC       AICc  
## -368.0870   736.1740   754.1740   785.4622   754.9600  
## 
## tau^2 (estimated amount of residual heterogeneity):     0.8747 (SE = 0.1062)
## tau (square root of estimated tau^2 value):             0.9352
## I^2 (residual heterogeneity / unaccounted variability): 86.28%
## H^2 (unaccounted variability / sampling variability):   7.29
## 
## Test for Residual Heterogeneity: 
## QE(df = 239) = 1476.4639, p-val < .0001
## 
## Test of Moderators (coefficient(s) 1:8): 
## QM(df = 8) = 31.5016, p-val = 0.0001
## 
## Model Results:
## 
##                estimate      se     zval    pval    ci.lb   ci.ub     
## FGAll            0.2507  0.1266   1.9801  0.0477   0.0026  0.4988    *
## FGDetrivorous    0.4061  0.2467   1.6462  0.0997  -0.0774  0.8896    .
## FGHerbivorous    0.1317  0.1328   0.9914  0.3215  -0.1286  0.3919     
## FGMycorrhiza     0.1362  0.3989   0.3414  0.7328  -0.6457  0.9181     
## FGOmnivorous     0.4591  0.2203   2.0834  0.0372   0.0272  0.8909    *
## FGParasitic     -0.3667  0.3696  -0.9921  0.3212  -1.0911  0.3577     
## FGPollinator     1.0532  0.2842   3.7060  0.0002   0.4962  1.6101  ***
## FGPredator       0.4183  0.1927   2.1708  0.0299   0.0406  0.7959    *
## 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## compare inclusion of moderators
(.9539-.9352)/.9352 ## explains an additional 2%
## [1] 0.01999572
## Produce a forest plot to determine the effect sizes for each study
forest(m1)

confint(m1)
## 
##        estimate   ci.lb   ci.ub
## tau^2    0.9099  0.7627  1.2443
## tau      0.9539  0.8733  1.1155
## I^2(%)  87.1393 85.0278 90.2587
## H^2      7.7756  6.6790 10.2656
## Check for publication bias
## The symetrical distriubtion suggests there is no publication bias
funnel(m1)

## Calculate rosenthals Failsafe number
fsn(yi, vi, data=meta.ready)
## 
## Fail-safe N Calculation Using the Rosenthal Approach 
## 
## Observed Significance Level: <.0001 
## Target Significance Level:   0.05 
## 
## Fail-safe N: 8147
# ## generate plot with spaces inbetween
# forest(m1, atransf=exp, cex=0.75, ylim=c(-1, 24),
#        order=order(meta.ready$GI.compare,meta.ready$taxa), rows=c(3:4,7,10:16,19:21),
# #         mlab="RE model for all studies", psize=1, slab= paste(meta.ready$Study, meta.ready$taxa, meta.ready$measure))
# 
# addpoly(res.w, row=18, cex=0.75, atransf=exp, mlab="RE model for green wall")
# addpoly(res.r, row= 9, cex=0.75, atransf=exp, mlab="RE model for green roof")
# addpoly(res.rd, row= 6, cex=0.75, atransf=exp, mlab="RE model for roadsides")
# addpoly(res.p, row= 2, cex=0.75, atransf=exp, mlab="RE model for retention ponds")